package com.hjk.common.aop.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.UUID;

@Aspect
@Slf4j
@Component
public class RedisLockAspect {


    @Autowired
    private StringRedisTemplate redisTemplate;

    @Value("${redis.lua.lock-script:if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then  return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end}")
    private String lockScript;

    @Value("${redis.lua.release-lock-script:if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end}")
    private String releaseLockScript;

    public boolean lock(String key, String requestId, Integer expiresTime) {
        Long result = redisTemplate.execute(
                new DefaultRedisScript<>(lockScript, Long.class),
                Collections.singletonList(key),
                requestId, String.valueOf(expiresTime));
        return 1 == result;
    }

    public boolean releaseLock(String key, String requestId) {
        if (key == null || requestId == null) {
            return false;
        }
        Long res = redisTemplate.execute(
                new DefaultRedisScript<>(releaseLockScript, Long.class),
                Collections.singletonList(key),
                requestId);
        return res != null && res == 1;
    }


    @Pointcut("@annotation(com.ur.common.aop.RedisLock)")
    public void addLockAnnotationPointcut() {

    }

    @Around(value = "addLockAnnotationPointcut()")
    public Object addKeyMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        //定义返回值
        Object proceed;
        //获取方法名称
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RedisLock redisLock = AnnotationUtils.findAnnotation(method, RedisLock.class);
        //前置方法 开始
        String logInfo = methodSignature.getName();
        String lockKey = redisLock.prefix() + redisLock.key();
        log.info("{}获取锁={}", logInfo, lockKey);
        // 保证 加锁者 和释放者 是同一个。
        String requestId = UUID.randomUUID().toString();
        boolean lockReleased = false;
        try {
            boolean lock = lock(lockKey, requestId, redisLock.expire());
            if (!lock) {
                return null;
            }
            // 目标方法执行
            proceed = joinPoint.proceed();
            boolean releaseLock = releaseLock(lockKey, requestId);
            lockReleased = true;
            return proceed;
        } catch (Exception exception) {
            log.error("{}执行异常,key = {},message={}, exception = {}", logInfo, lockKey, exception.getMessage(), exception);
            throw exception;
        } finally {
            if (!lockReleased) {
                log.info("{}异常终止释放锁={}", logInfo, lockKey);
                boolean releaseLock = releaseLock(lockKey, requestId);
                log.info("releaseLock={}", releaseLock);
            }
        }
    }
}
